package framework.fault;

import framework.grid.NodePeer;
import framework.grid.data.IRemoteNodeData;
import framework.grid.data.IRemoteNodeMetaData;
import framework.grid.data.RemoteNodeData;
import framework.grid.data.RemoteNodeMetaData;
import framework.system.BackEnd;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class FaultTolerantPeer extends UnicastRemoteObject implements IFaultTolerantPeer {

	private BackEnd backEnd;

	
	private IFaultTolerantPeer leftPeer;
	private String leftName;
	private NodePeer leftDataNode;
	private IFaultDetectionPeer leftFaultNode;
	
	private IFaultTolerantPeer rightPeer;
	private String rightName;
	private NodePeer rightDataNode;
	private IFaultDetectionPeer rightFaultNode;


	private String name;
	private NodePeer dataNode;
	private FaultDetectionPeer faultNode;
	//TODO refactor, extract FaultTolerancePeer

	public FaultTolerantPeer(BackEnd backEnd, String name, int failurePercentage) 
	throws RemoteException {
		super();
		this.name = name;
		this.backEnd = backEnd;
		this.dataNode = NodePeer.createInstance(name, new IFaultTolerantPeer[]{this});
		this.faultNode = FaultDetectionPeer.createInstance(this,this.name,failurePercentage);
		this.leftPeer = this;
		this.leftName = name;
		this.rightPeer = this;
		this.rightName = name;
	}

	
	@Override
	public void resetState() throws RemoteException {
		this.dataNode = NodePeer.copyInstance(this.dataNode, new IFaultTolerantPeer[]{this,leftPeer,rightPeer});
	}

	@Override
	public void resetRefs() throws RemoteException {
		if (!this.leftName.equals(name))
			this.faultNode.removePeer(leftName);
		if (!this.rightName.equals(name))
			this.faultNode.removePeer(rightName);


		this.leftName = this.leftPeer.getName();
		this.leftDataNode = this.leftPeer.getDataNode();
		this.leftFaultNode = this.leftPeer.getFaultNode();
		
		this.rightName = this.rightPeer.getName();
		this.rightDataNode = this.rightPeer.getDataNode();
		this.rightFaultNode = this.rightPeer.getFaultNode();

		
		if (!this.leftName.equals(name))
			this.faultNode.addPeer(leftName, leftFaultNode);
		if (!this.rightName.equals(name))
			this.faultNode.addPeer(rightName, rightFaultNode);
	}

	//FIXME JOIN
	public void join() throws RemoteException {
		IFaultTolerantPeer[] otherPeers = this.backEnd.publishIsolated(this);
		for (IFaultTolerantPeer otherPeer: otherPeers) {
			if (otherPeer != this)
				if (otherPeer.join(this)) {
					this.resetState();
					this.leftPeer.resetState();
					this.rightPeer.resetState();
					this.resetRefs();
					this.leftPeer.resetRefs();
					this.rightPeer.resetRefs();
					break;
				}
		}
		
		this.backEnd.joinGrid(this.dataNode);
	}

	public void leave() throws RemoteException {
		this.backEnd.leaveGrid(this.dataNode);

		this.leftPeer.setRight(this.rightPeer);
		this.rightPeer.setLeft(this.leftPeer);
		this.leftPeer.resetState();
		this.rightPeer.resetState();
		this.leftPeer.resetRefs();
		this.rightPeer.resetRefs();
		
		this.crash();
	}
	
	public void crash() throws RemoteException {
		FaultDetectionPeer.destroyInstance(this.faultNode);
		this.leftPeer = null;
		this.rightPeer = null;
	}
	
	public void leftCrashed() throws RemoteException {
		this.leftPeer = null;
		this.backEnd.leaveGrid(leftDataNode);

		IFaultTolerantPeer[] otherPeers = this.backEnd.publishMissingLeft(this);
		for (IFaultTolerantPeer otherPeer: otherPeers) {
			if (otherPeer.reConnect(Side.RIGHT, this, false)) {
				this.resetState();
				this.leftPeer.resetState();
				this.resetRefs();
				this.leftPeer.resetRefs();
				break;
			}
		}
	}
	
	public void rightCrashed() throws RemoteException {
		this.rightPeer = null;
		this.backEnd.leaveGrid(rightDataNode);

		IFaultTolerantPeer[] otherPeers = this.backEnd.publishMissingRight(this);
		for (IFaultTolerantPeer otherPeer: otherPeers) {
			if (otherPeer.reConnect(Side.LEFT, this, false)) {
				this.resetState();
				this.rightPeer.resetState();
				this.resetRefs();
				this.rightPeer.resetRefs();
				break;
			}
		}
	}
	
	public void bothCrashed() throws RemoteException {
		this.rightPeer = this;
		this.leftPeer = this;
		this.backEnd.leaveGrid(rightDataNode);

		this.resetState();
		this.resetRefs();

		this.backEnd.publishIsolated(this);
	}

	public void otherCrashed(String crashedPeer) {
		try {
			if (crashedPeer.equals(leftName) && leftName.equals(rightName)) {
				System.out.println("<"+name+">: "+"LEFT<"+leftName+"> crashed");
				System.out.println("<"+name+">: "+"RIGHT<"+rightName+"> crashed");
				bothCrashed();
			} else if (crashedPeer.equals(leftName)) {
				System.out.println("<"+name+">: "+"LEFT<"+leftName+"> crashed");
				leftCrashed();
			} else if (crashedPeer.equals(rightName)) {
				System.out.println("<"+name+">: "+"RIGHT<"+rightName+"> crashed");
				rightCrashed();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


	@Override
	public IRemoteNodeData createRemoteNodeData() throws RemoteException {
		return new RemoteNodeData();
	}
	@Override
	public IRemoteNodeMetaData createRemoteNodeMetaData()
	throws RemoteException {
		return new RemoteNodeMetaData();
	}


	@Override
	public boolean join(IFaultTolerantPeer joiningPeer) throws RemoteException {
		if (this.leftPeer != null && this.rightPeer != null) {
			if (this.rightPeer.reConnect(Side.LEFT, joiningPeer, true))
				return this.reConnect(Side.RIGHT, joiningPeer, true);
		}
		return false;
	}

	@Override
	public NodePeer getDataNode() throws RemoteException {
		return dataNode;
	}
	@Override
	public IFaultDetectionPeer getFaultNode() throws RemoteException {
		return faultNode;
	}
	@Override
	public boolean setLeft(IFaultTolerantPeer peer) throws RemoteException {
		this.leftPeer = peer;
		System.out.println("<"+this.name+">: "+"LEFT="+this.leftPeer.getName());
		return true;
	}
	@Override
	public boolean setRight(IFaultTolerantPeer peer) throws RemoteException {
		this.rightPeer = peer;
		System.out.println("<"+this.name+">: "+"RIGHT="+this.rightPeer.getName());
		return true;
	}

	@Override
	public boolean reConnect(Side side, IFaultTolerantPeer peer, boolean forceConnect)
	throws RemoteException {
		if (side == Side.LEFT) {
			if (this.leftPeer==null || forceConnect)
				if (this.setLeft(peer))
					return peer.setRight(this);
		} else if (side == Side.RIGHT) {
			if (this.rightPeer==null || forceConnect)
				if (this.setRight(peer))
					return peer.setLeft(this);
		}
		return false;
	}

	@Override
	public String getName() throws RemoteException {
		return this.name;
	}	
}
